/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 carryover_data.cpp

	$Header: /heroes4/carryover_data.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#include "carryover_data.h"

#include <iterator>

#include "enum_operations.h"
#include "hero_class_properties.h"
#include "sized_int_types.h"
#include "streambuf_operators.h"
#include "string_insensitive_compare.h"

// --------------------------------------------------------------------------
// Unnamed namespace
// --------------------------------------------------------------------------

namespace
{

	int const k_current_format_version = 4;
	int const k_hero_current_format_version = 2;

	typedef std::vector< t_hero_carryover_data_ptr > t_hero_pool;

	// --------------------------------------------------------------------------
	// Sum up the skill values for the given hero
	// --------------------------------------------------------------------------
	int get_power( t_hero_carryover_data const & hero_data )
	{
		int result = 0;
		t_skill_type skill;
		for ( skill = t_skill_type( 0 ); skill < k_skill_type_count; enum_incr( skill ) )
			result += hero_data.get_mastery( skill ) + 1;
		return result;
	}

	// --------------------------------------------------------------------------
	// t_sort_by_power function object
	// --------------------------------------------------------------------------

	struct t_sort_by_power
	{
		bool operator()(
			t_hero_carryover_data_ptr const &	first,
			t_hero_carryover_data_ptr const &	second )
		{
			return get_power( *first ) < get_power( *second );
		}
	};

	// --------------------------------------------------------------------------
	// Non-member functions
	// --------------------------------------------------------------------------

	// --------------------------------------------------------------------------
	// The standard library does not provide a method for removing an arbitrary
	// element from a heap so here it is
	// --------------------------------------------------------------------------

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	inline size_t compute_parent_index( size_t index )
	{
		return ( index + 1 ) / 2 - 1;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	inline size_t compute_child_index( size_t index )
	{
		return ( index + 1 ) * 2 - 1;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	template < typename t_iter, typename t_compare >
	void up_heap( t_iter begin, t_iter iter, t_compare compare )
	{
		assert( begin <= iter );

		size_t index = iter - begin;

		while ( index > 0 )
		{
			size_t parent_index = compute_parent_index( index );
			t_iter parent_iter = begin + parent_index;

			if ( !compare( *parent_iter, *iter ) )
				break;
			
			std::iter_swap( parent_iter, iter );

			index = parent_index;
			iter = parent_iter;
		}
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	template < typename t_iter, typename t_compare >
	void down_heap( t_iter begin, t_iter end, t_iter iter, t_compare compare )
	{
		assert( begin <= iter && iter < end );

		size_t size = end - begin;
		size_t index = iter - begin;

		while ( true )
		{
			size_t child_index;
			t_iter child_iter;

			size_t first_child_index = compute_child_index( index );
			if ( first_child_index >= size )
				break;
			t_iter first_child_iter = begin + first_child_index;

			size_t second_child_index = first_child_index + 1;
			if ( second_child_index >= size )
			{
				child_index = first_child_index;
				child_iter = first_child_iter;
			}
			else
			{
				t_iter second_child_iter = begin + second_child_index;

				if ( compare( *first_child_iter, *second_child_iter ) )
				{
					child_index = second_child_index;
					child_iter = second_child_iter;
				}
				else
				{
					child_index = first_child_index;
					child_iter = first_child_iter;
				}
			}

			if ( !compare( *iter, *child_iter ) )
				break;

			std::iter_swap( child_iter, iter );

			index = child_index;
			iter = child_iter;
		}
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	template < typename t_iter, typename t_compare >
	void adjust_heap( t_iter begin, t_iter end, t_iter iter, t_compare compare )
	{
		assert( begin <= iter && iter < end );

		size_t index = iter - begin;
		if ( index > 0 && compare( *( begin + compute_parent_index( index ) ), *iter ) )
			up_heap< t_iter, t_compare >( begin, iter, compare );
		else
			down_heap< t_iter, t_compare >( begin, end, iter, compare );
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	template < typename t_iter, typename t_compare >
	void pop_heap_element( t_iter begin, t_iter end, t_iter iter, t_compare compare )
	{
		assert( begin <= iter && iter < end );

		if ( iter == --end )
			return;

		std::iter_swap( iter, end );

		adjust_heap< t_iter, t_compare >( begin, end, iter, compare );
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	bool read_hero_pool(
		std::streambuf &	stream,
		t_hero_pool &		hero_pool,
		int					format_version )
	{
		int count = get< t_uint16 >( stream );

		t_hero_pool new_hero_pool;
		new_hero_pool.reserve( count );

		while ( count > 0 )
		{
			t_hero_carryover_data_ptr hero_data_ptr( new t_hero_carryover_data );
			if ( !hero_data_ptr->read( stream, format_version ) )
				return false;

			new_hero_pool.push_back( hero_data_ptr );

			--count;
		}

		hero_pool.swap( new_hero_pool );
		return true;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	bool write_hero_pool(
		std::streambuf &	stream,
		t_hero_pool const &	hero_pool )
	{
		put< t_uint16 >( stream, hero_pool.size() );

		t_hero_pool::const_iterator hero_ptr_iter = hero_pool.begin();
		t_hero_pool::const_iterator hero_ptr_end = hero_pool.end();
		for ( ; hero_ptr_iter != hero_ptr_end; ++hero_ptr_iter )
		{
			if ( !( *hero_ptr_iter )->write( stream ) )
				return false;
		}

		return true;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	void add_hero_to_pool(
		t_hero_pool &				hero_pool,
		t_hero_carryover_data_ptr	hero_data_ptr )
	{
		assert( hero_data_ptr.get() != 0 );

		hero_pool.push_back( hero_data_ptr );
		std::push_heap( hero_pool.begin(), hero_pool.end(), t_sort_by_power() );
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	t_hero_carryover_data_ptr retrieve_most_powerful_hero_from_pool(
		t_hero_pool &	hero_pool )
	{
		if ( hero_pool.empty() )
			return t_hero_carryover_data_ptr();

		std::pop_heap( hero_pool.begin(), hero_pool.end(), t_sort_by_power() );
		t_hero_carryover_data_ptr result = hero_pool.back();
		hero_pool.pop_back();

		return result;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	t_hero_carryover_data_ptr retrieve_named_hero_from_pool(
		std::string const &	name,
		t_hero_pool &		hero_pool )
	{
		t_hero_pool::iterator hero_ptr_iter = hero_pool.begin();
		t_hero_pool::iterator hero_ptr_end = hero_pool.end();
		for ( ; true; ++hero_ptr_iter )
		{
			if ( hero_ptr_iter == hero_ptr_end )
				return t_hero_carryover_data_ptr();

			t_hero_carryover_data const & hero = **hero_ptr_iter;
			if ( hero.is_named_by_map() && string_insensitive_compare( hero.get_name(), name ) == 0 )
				break;
		}

		pop_heap_element( hero_pool.begin(), hero_ptr_end, hero_ptr_iter, t_sort_by_power() );
		t_hero_carryover_data_ptr result = hero_pool.back();
		hero_pool.pop_back();
		return result;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	t_hero_pool copy( t_hero_pool const & source )
	{
		t_hero_pool result;
		result.reserve( source.size() );

		t_hero_pool::const_iterator hero_ptr_iter = source.begin();
		t_hero_pool::const_iterator hero_ptr_end = source.end();
		for ( ; hero_ptr_iter != hero_ptr_end; ++hero_ptr_iter )
			result.push_back( new t_hero_carryover_data( **hero_ptr_iter ) );

		return result;
	}

} // Unnamed namespace

// --------------------------------------------------------------------------
// t_hero_carryover_data members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_hero_carryover_data::t_hero_carryover_data()
	:	m_alignment( k_town_none ),
		m_biography_id( 0 ),
		m_experience( 0 ),
		m_class( k_hero_class_none ),
		m_male( true ),
		m_named_by_map( false ),
		m_portrait_id( 0 )
{
	std::fill( m_bonuses, m_bonuses + k_stat_type_count, 0 );
	std::fill( m_skill_masteries, m_skill_masteries + k_skill_type_count, k_mastery_none );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_hero_carryover_data::read( std::streambuf & stream, int real_format_version )
{
	int format_version = get< t_uint16 >( stream );
	if ( format_version < 0 || format_version > k_hero_current_format_version )
		return false;

	if ( real_format_version >= 0 )
		format_version = real_format_version;

	m_alignment = t_town_type( get< t_int8 >( stream ) );
	if ( m_alignment < 0 || m_alignment >= k_town_type_count )
		return false;

	t_artifact_slot slot;
	for ( slot = t_artifact_slot( 0 ); slot < k_artifact_slot_hero_count; enum_incr( slot ) )
	{
		if ( !m_artifacts[ slot ].read( stream ) )
			return false;
	}

	int backpack_size = get< t_uint16 >( stream );
	t_backpack new_backpack;
	new_backpack.reserve( backpack_size );
	while ( backpack_size > 0 )
	{
		t_artifact new_artifact;
		if ( !new_artifact.read( stream ) )
			return false;
		new_backpack.push_back( new_artifact );
		--backpack_size;
	}
	m_backpack.swap( new_backpack );

	if ( !read_string16( stream, m_biography ) )
		return false;

	m_biography_id = get< t_int16 >( stream );
	m_experience = get< t_uint32 >( stream );

	m_class = t_hero_class( get< t_uint8 >( stream ) );
	if ( m_class < 0 || m_class >= k_hero_class_count )
		return false;

	m_male = get< t_int8 >( stream ) != 0;

	if ( !read_string16( stream, m_name ) )
		return false;

	m_named_by_map = get< t_int8 >( stream ) != 0;
	m_portrait_id = get< t_uint16 >( stream );
	if (format_version >= 1)
		m_use_spellcaster_model = get<bool>( stream );
	else
		m_use_spellcaster_model = is_spellcaster( m_class );

	t_skill_type skill;
	for ( skill = t_skill_type( 0 ); skill < k_skill_type_count; enum_incr( skill ) )
	{
		t_skill_mastery mastery = t_skill_mastery( get< t_int8 >( stream ) );
		if ( mastery < k_mastery_none || mastery >= k_skill_mastery_count )
			return false;
		m_skill_masteries[ skill ] = mastery;
	}

	if ( !::read( stream, m_spellbook ) )
		return false;

	if ( format_version >= 2 )
	{
		t_stat_type stat_type;
		for ( stat_type = t_stat_type( 0 ); stat_type < k_stat_type_count; enum_incr( stat_type ) )
			m_bonuses[ stat_type ] = get< t_int32 >( stream );
	}
	else
		std::fill( m_bonuses, m_bonuses + k_stat_type_count, 0 );

	return true;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_hero_carryover_data::write( std::streambuf & stream ) const
{
	put< t_uint16 >( stream, k_hero_current_format_version );
	put< t_int8 >( stream, m_alignment );

	t_artifact_slot slot;
	for ( slot = t_artifact_slot( 0 ); slot < k_artifact_slot_hero_count; enum_incr( slot ) )
	{
		if ( !m_artifacts[ slot ].write( stream ) )
			return false;
	}

	put< t_uint16 >( stream, m_backpack.size() );
	t_backpack::const_iterator backpack_end = m_backpack.end();
	t_backpack::const_iterator backpack_iter = m_backpack.begin();
	for ( ; backpack_iter != backpack_end; ++backpack_iter )
	{
		if ( !backpack_iter->write( stream ) )
			return false;
	}

	if ( !write_string16( stream, m_biography ) )
		return false;

	put< t_int16 >( stream, m_biography_id );
	put< t_uint32 >( stream, m_experience );
	put< t_uint8 >( stream, m_class );
	put< t_int8 >( stream, m_male );

	if ( !write_string16( stream, m_name ) )
		return false;

	put< t_int8 >( stream, m_named_by_map );
	put< t_uint16 >( stream, m_portrait_id );
	put< bool >( stream, m_use_spellcaster_model );

	t_skill_type skill;
	for ( skill = t_skill_type( 0 ); skill < k_skill_type_count; enum_incr( skill ) )
		put< t_int8 >( stream, m_skill_masteries[ skill ] );

	if ( !::write( stream, m_spellbook ) )
		return false;

	t_stat_type stat_type;
	for ( stat_type = t_stat_type( 0 ); stat_type < k_stat_type_count; enum_incr( stat_type ) )
		put< t_int32 >( stream, m_bonuses[ stat_type ] );

	return true;
}

// --------------------------------------------------------------------------
// t_carryover_data members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_carryover_data::t_carryover_data( t_carryover_data const & other )
	:	m_scenario_scores( other.m_scenario_scores )
{
	std::copy( other.m_artifact_pool, other.m_artifact_pool + k_artifact_type_count, m_artifact_pool );
	t_player_color color;
	for ( color = t_player_color( 0 ); color < k_active_player_color_count; enum_incr( color ) )
		copy( other.m_hero_pools[ color ] ).swap( m_hero_pools[ color ] );
	copy( other.m_human_hero_pool ).swap( m_human_hero_pool );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_carryover_data::add(
	t_hero_data_ptr	hero_ptr,
	t_player_color	player_color )
{
	assert( player_color >= 0 && player_color < k_active_player_color_count );
	add_hero_to_pool( m_hero_pools[ player_color ], hero_ptr );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_carryover_data::add_for_human( t_hero_data_ptr hero_ptr )
{
	add_hero_to_pool( m_human_hero_pool, hero_ptr );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_carryover_data::add( t_artifact const & artifact )
{
	m_artifact_pool[ artifact.get_icon() ].push_back( artifact );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_carryover_data::read( std::streambuf & stream )
{
	int format_version = get< t_uint16 >( stream );
	if ( format_version < 0 || format_version > k_current_format_version )
		return false;

	// Read in the artifacts
	int artifact_count = get< t_uint16 >( stream );
	while ( artifact_count > 0 )
	{
		t_artifact artifact;
		if ( !artifact.read( stream ) )
			return false;

		m_artifact_pool[ artifact.get_icon() ].push_back( artifact );

		--artifact_count;
	}

	// Read in the hero pools
	int hero_format_version = -1;
	if ( format_version == 1 )
		hero_format_version = 1;

	t_player_color color;
	for ( color = t_player_color( 0 ); color < k_active_player_color_count; enum_incr( color ) )
	{
		if ( !read_hero_pool( stream, m_hero_pools[ color ], hero_format_version ) )
			return false;
	}

	if ( !read_hero_pool( stream, m_human_hero_pool, hero_format_version ) )
		return false;

	// Read in the scenario scores
	if ( format_version >= 3 )
	{
		int score_count = get< t_uint16 >( stream );
		t_score_list new_scenario_scores;
		new_scenario_scores.reserve( score_count );
		while ( score_count > 0 )
		{
			t_scenario_score score;
			score.m_score = get< t_int32 >( stream );

			if (format_version >= 4) {
				score.m_days = get< t_int32 >( stream );
			} else {
				score.m_days = 0;
			}

			new_scenario_scores.push_back( score );
			--score_count;
		}
		m_scenario_scores.swap( new_scenario_scores );
	}

	return true;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::vector< t_artifact > t_carryover_data::retrieve_artifacts(
	t_artifact_set const &	artifact_set )
{
	int result_size = 0;

	t_artifact_type artifact_type;
	for (	artifact_type = t_artifact_type( 0 );
			artifact_type < k_artifact_type_count;
			enum_incr( artifact_type ) )
	{
		if ( !artifact_set[ artifact_type ] )
			continue;

		result_size += m_artifact_pool[ artifact_type ].size();
	}

	std::vector< t_artifact > result;
	result.reserve( result_size );

	for (	artifact_type = t_artifact_type( 0 );
			artifact_type < k_artifact_type_count;
			enum_incr( artifact_type ) )
	{
		if ( !artifact_set[ artifact_type ] )
			continue;

		std::vector< t_artifact > & artifacts = m_artifact_pool[ artifact_type ];
		std::copy( artifacts.begin(), artifacts.end(), std::back_inserter( result ) );
		std::vector< t_artifact >().swap( artifacts );
	}

	return result;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_carryover_data::t_hero_data_ptr
t_carryover_data::retrieve_most_powerful_hero( t_player_color player_color )
{
	assert( player_color >= 0 && player_color < k_player_color_count );
	return retrieve_most_powerful_hero_from_pool( m_hero_pools[ player_color ] );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_carryover_data::t_hero_data_ptr
t_carryover_data::retrieve_most_powerful_hero_for_human()
{
	return retrieve_most_powerful_hero_from_pool( m_human_hero_pool );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_carryover_data::t_hero_data_ptr
t_carryover_data::retrieve_named_hero( std::string const & name )
{
	t_hero_data_ptr result = retrieve_named_hero_from_pool( name, m_human_hero_pool );
	if ( result.get() != 0 )
		return result;

	t_player_color color;
	for ( color = t_player_color( 0 ); color < k_active_player_color_count; enum_incr( color ) )
	{
		result = retrieve_named_hero_from_pool( name, m_hero_pools[ color ] );
		if ( result.get() != 0 )
			return result;
	}

	return result;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_carryover_data::write( std::streambuf & stream ) const
{
	put< t_uint16 >( stream, k_current_format_version );

	// Count the total number of artifacts
	int artifact_count = 0;
	t_artifact_type artifact_type;
	for ( artifact_type = t_artifact_type( 0 ); artifact_type < k_artifact_type_count; enum_incr( artifact_type ) )
		artifact_count += m_artifact_pool[ artifact_type ].size();

	// Write out the artifacts
	put< t_uint16 >( stream, artifact_count );
	for ( artifact_type = t_artifact_type( 0 ); artifact_type < k_artifact_type_count; enum_incr( artifact_type ) )
	{
		typedef std::vector< t_artifact > t_artifact_list;
		t_artifact_list const & artifacts = m_artifact_pool[ artifact_type ];
		
		typedef t_artifact_list::const_iterator t_artifact_iter;
		t_artifact_iter artifact_end = artifacts.end();
		t_artifact_iter artifact_iter = artifacts.begin();
		for ( ; artifact_iter != artifact_end; ++artifact_iter )
		{
			if ( !artifact_iter->write( stream ) )
				return false;
		}
	}

	// Write out the hero pools
	t_player_color color;
	for ( color = t_player_color( 0 ); color < k_active_player_color_count; enum_incr( color ) )
	{
		if ( !write_hero_pool( stream, m_hero_pools[ color ] ) )
			return false;
	}

	if ( !write_hero_pool( stream, m_human_hero_pool ) )
		return false;

	// Write out the scenario scores
	put< t_uint16 >( stream, m_scenario_scores.size() );
	t_score_list::const_iterator score_end = m_scenario_scores.end();
	t_score_list::const_iterator score_iter = m_scenario_scores.begin();
	for ( ; score_iter != score_end; ++score_iter )
	{
		put< t_int32 >( stream, score_iter->m_score );
		put< t_int32 >( stream, score_iter->m_days );
	}

	return true;
}
